﻿/*******************************************************************************************************************************************
Modification History:
********************************************************************************************************************************************
Date		Author		    Description
********************************************************************************************************************************************
05/07/2011	Brian Kuhn	    Created WeakAction class
*******************************************************************************************************************************************/
using System;

namespace Concise
{
    /// <summary>
    /// Represents a weak reference to an <see cref="Action"/> which references a target object 
    /// while still allowing the target to be reclaimed by garbage collection.
    /// </summary>
    public class WeakAction : IEquatable<WeakAction>
    {
        //=======================================================================================================
        //	Constructors
        //=======================================================================================================
        #region WeakAction(object target, Action method)
        /// <summary>
        /// Initializes a new instance of the <see cref="WeakAction"/> class 
        /// using the specified target and action.
        /// </summary>
        /// <param name="target">The object that can hold a reference to the <paramref name="method"/>.</param>
        /// <param name="method">A method that has no parameters and does not return a value.</param>
        public WeakAction(object target, Action method)
        {
            _weakReferenceTarget    = new WeakReference(target);
            _weakActionMethod       = method;
        }
        #endregion

        //=======================================================================================================
        //	Public Properties
        //=======================================================================================================
        #region IsAlive
        /// <summary>
        /// Gets an indication whether the referenced target object has been garbage collected.
        /// </summary>
        /// <value>
        /// <see langword="true"/> if the <see cref="Target"/> referenced by the current <see cref="WeakAction"/> object 
        /// has not been garbage collected and is still accessible; otherwise, <see langword="false"/>.
        /// </value>
        public bool IsAlive
        {
            get
            {
                if (_weakReferenceTarget == null)
                {
                    return false;
                }

                return _weakReferenceTarget.IsAlive;
            }
        }
        #endregion

        #region Method
        /// <summary>
        /// Gets the delegate method referenced by this weak action.
        /// </summary>
        /// <value>
        /// The method that has no parameters and does not return a value 
        /// that is referenced by the current <see cref="WeakAction"/> object.
        /// </value>
        public Action Method
        {
            get
            {
                return _weakActionMethod;
            }
        }
        private readonly Action _weakActionMethod;
        #endregion

        #region Target
        /// <summary>
        /// Gets the target object referenced by this weak action.
        /// </summary>
        /// <value>
        /// The target object referenced by the current <see cref="WeakAction"/> object.
        /// </value>
        public object Target
        {
            get
            {
                if (_weakReferenceTarget == null)
                {
                    return null;
                }

                return _weakReferenceTarget.Target;
            }
        }
        private WeakReference _weakReferenceTarget;
        #endregion

        //=======================================================================================================
        //	Public Methods
        //=======================================================================================================
        #region Execute()
        /// <summary>
        /// Executes the delegate method referenced by this weak action.
        /// </summary>
        /// <remarks>
        /// The <see cref="Method"/> will only be executed if the referenced target object has not been garbage collected.
        /// </remarks>
        public void Execute()
        {
            if (_weakActionMethod != null && this.IsAlive)
            {
                _weakActionMethod();
            }
        }
        #endregion

        #region Release()
        /// <summary>
        /// Releases the target object referenced by this weak action, allowing it to be reclaimed by garbage collection.
        /// </summary>
        public void Release()
        {
            _weakReferenceTarget = null;
        }
        #endregion

        //=======================================================================================================
        //  IEquatable<WeakAction> Implementation
        //=======================================================================================================
        #region Equals(WeakAction other)
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns><see langword="true"/> if the current object is equal to the other parameter; otherwise, <see langword="false"/>.</returns>
        public bool Equals(WeakAction other)
        {
            if (other == null)
            {
                return false;
            }

            if (!Action.Equals(this.Method, other.Method))
            {
                return false;
            }
            else if (!Object.Equals(this.Target, other.Target))
            {
                return false;
            }

            return true;
        }
        #endregion

        #region Equals(object obj)
        /// <summary>
        /// Determines whether the specified <see cref="Object"/> is equal to the current <see cref="Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="Object"/> to compare with the current <see cref="Object"/>.</param>
        /// <returns>
        /// <see langword="true"/> if the specified <see cref="Object"/> is equal to the current <see cref="Object"/>; otherwise, <see langword="false"/>.
        /// </returns>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as WeakAction);
        }
        #endregion

        #region Equals(WeakAction first, WeakAction second)
        /// <summary>
        /// Determines whether two specified <see cref="WeakAction"/> objects have the same value.
        /// </summary>
        /// <param name="first">The first object to compare, or <see langword="null"/>.</param>
        /// <param name="second">The second object to compare, or <see langword="null"/>.</param>
        /// <returns>
        /// <see langword="true"/> if the value of <paramref name="first"/> object is the same as the value of the <paramref name="second"/> object; otherwise, <see langword="false"/>.
        /// </returns>
        public static bool Equals(WeakAction first, WeakAction second)
        {
            if (first == null && second != null)
            {
                return false;
            }

            if(first != null && second == null)
            {
                return false;
            }

            if (first == null && second == null)
            {
                return true;
            }

            return first.Equals(second);
        }
        #endregion

        #region GetHashCode()
        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        /// <a href="http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx"/>
        public override int GetHashCode()
        {
            int methodHashCode  = this.Method != null ? this.Method.GetHashCode() : 0;
            int targetHashCode  = this.Target != null ? this.Target.GetHashCode() : 0;

            /*
             * The 23 and 37 are arbitrary numbers which are co-prime.
             * 
             * The benefit of the below over the XOR (^) method is that if you have a type 
             * which has two values which are frequently the same, XORing those values 
             * will always give the same result (0) whereas the above will 
             * differentiate between them unless you're very unlucky.
            */
            int hashCode    = 23;
            hashCode        = hashCode * 37 + methodHashCode;
            hashCode        = hashCode * 37 + targetHashCode;
            
            return hashCode;
        }
        #endregion
    }
}
